﻿/////////////////////////////////////////////////////////////////////////
// MIT License
//
// Copyright (c) 2000-2011 OpenNETCF Consulting, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  
////////////////////////////////////////////////////////////////////////


#region Using directives

using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using Microsoft.WindowsCE.Forms;
using System.Drawing.Imaging;
using System.Reflection;
#endregion

namespace OpenNETCF.Windows.Forms
{

    #region PopupMenu class

	/// <summary>
	/// Represents a owner drawn shortcut menu.
    /// </summary>
    public class PopupMenu : ContextMenu
    {     

        #region Fields

        internal PopupMenuItemCollection itemList;
        internal Hashtable parentList;
		private ArrayList itemsArr;
        internal int menuWidth;
        private IntPtr hMainMenu;
        private MessagePump message;
        private ImageList imageList;
        private Image backgroundImage;
		private int menuIndex = 0;
		internal Font font;
		private const int SEPARATOR_ID = 11111;
        /// <summary>
        /// The dpi settings
        /// </summary>
        protected const int DPISETTING = 96;
		private bool rebuildMenu = true;
        private Hashtable menuList;
        private Assembly m_callingAssembly = null;
		//private bool showBand = false;
#if BETA
        DateTime betaExpire = new DateTime(2006, 12, 30);
#endif
		
        #endregion

        #region Constructors

		/// <summary>
		/// Initializes a new instance of the OpenNETCF.Windows.Forms.PopupMenu class with no menu items specified.
		/// </summary>
        public PopupMenu()
        {
            hMainMenu = new IntPtr(0);
            parentList = new Hashtable();
			itemsArr = new ArrayList();
            itemList = new PopupMenuItemCollection(this);
			font = new Font("Tahoma", 8F, FontStyle.Bold);
            message = new MessagePump(this);
            menuList = new Hashtable();

            
        }

        #endregion

		#region public methods

		/// <summary>
		/// Displays the popup menu at the specified position.
		/// </summary>
        /// <param name="ctrl">A System.Windows.Forms.Control object that specifies the control with which this shortcut menu is associated. </param>
		/// <param name="pos">A System.Drawing.Point object that specifies the coordinates at which to display the menu. These coordinates are specified relative to the client coordinates of the control</param>
        public override void Show(Control ctrl, System.Drawing.Point pos)
        {
            //System.Drawing.Point pt = ctrl.PointToScreen(pos);
            this.Show(pos);
        }

		/// <summary>
		/// Displays the popup menu at the specified position.
		/// </summary>
		/// <param name="pos">A System.Drawing.Point object that specifies the coordinates at which to display the menu. These coordinates are specified relative to the client coordinates of the control</param>
        public void Show(System.Drawing.Point pos)
        {
			if (rebuildMenu)
			{
				if ((int)hMainMenu > 0)
					Native.DeleteMenu(hMainMenu, 0, Native.MF_BYPOSITION);

				hMainMenu = Native.CreatePopupMenu();
                menuList.Clear();
				parentList.Clear();
				menuIndex = 0;
                itemsArr.Clear();
				BuildMenu(hMainMenu, itemList);

				rebuildMenu = false;
			}

            int selectedIndex = Native.TrackPopupMenuEx(hMainMenu, Native.TPM_RETURNCMD | Native.TPM_LEFTALIGN | Native.TPM_TOPALIGN, pos.X, pos.Y, message.Hwnd, IntPtr.Zero);
           
            PopupMenuItem item = (PopupMenuItem)menuList[selectedIndex.ToString()];
            if (item != null)
            {
                if(item.Enabled)
                    item.RaiseClick(new EventArgs());
            }          
                  
        }

		#endregion

		#region virtual methods

        /// <summary>
        /// Invokes the Popup event
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPopup(EventArgs e)
        {
            Native.POINT point = new Native.POINT();
            Native.GetCursorPos(ref point);

            this.Show(Control.MousePosition);
        }    

		#endregion

		#region properties		

		/// <summary>
		/// Gets or sets the background image displayed in the control.
		/// </summary>
        public Image BackgroundImage
        {
            get
            {
                return backgroundImage;
            }
            set
            {
                backgroundImage = value;
            }
        }

		/// <summary>
		/// Gets a value indicating the collection of PopupMenuItem objects associated with the menu.
		/// </summary>
        public new PopupMenuItemCollection MenuItems
        {
            get
            {
                return itemList;
            }
        }

		/// <summary>
		/// Gets or sets the ImageList to use when displaying items as small icons in the control.
		/// </summary>
        public ImageList ImageList
        {
            get
            {
                return imageList;
            }
            set
            {
                imageList = value;
            }
        }

        #endregion

		#region internal methods

		internal void RemoveMenu(int index)
		{
			PopupMenuItem mItem = itemList[index] as PopupMenuItem;
            try
            {
                Native.DeleteMenu(hMainMenu, mItem.nativeIndex, Native.MF_BYPOSITION);
            }
            catch { }
		}

		
		internal SizeF MeasureString(string text, Font font)
		{
			Native.SIZE sz = new Native.SIZE();
			int fit = 0;
			IntPtr hDC = Native.CreateCompatibleDC(IntPtr.Zero);
			IntPtr hOldFont = Native.SelectObject(hDC, font.ToHfont());
			int width = 100;
			Native.GetTextExtentExPoint(hDC, text, text.Length, width, out fit, null, ref sz);
			Native.SelectObject(hDC, hOldFont);
			Native.DeleteObject(hDC);
            return new SizeF(sz.width, sz.height);
		}


		#endregion

		#region helper methods

		private Color GetBackColor(Bitmap image)
		{
			return image.GetPixel(0, 0);
		}

		private void BuildMenu(IntPtr hMenu, PopupMenuItemCollection itemList)
        {          
			menuIndex++;

            for(int i=0;i<itemList.Count;i++)
            {
                PopupMenuItem mItem = (PopupMenuItem)itemList[i];

				mItem.nativeIndex = menuIndex;

                if (mItem.MenuItems.Count > 0)
                {
                    IntPtr hSubMenu = Native.CreatePopupMenu();
                    mItem.m_hParent = hMenu;
					Native.AppendMenu(hMenu, Native.MF_POPUP | Native.MF_OWNERDRAW, (int)hSubMenu, menuIndex);    
                    itemsArr.Add(mItem);
                    //menuList.Add(hSubMenu.ToString(), mItem);
                    BuildMenu(hSubMenu, mItem.MenuItems);
                }
                else
                {

                    if (mItem.Text == "-") //Separator
                    {
						Native.AppendMenu(hMenu, Native.MF_OWNERDRAW | Native.MF_SEPARATOR, (int)SEPARATOR_ID, menuIndex);
                    }
                    else
                    {
                        Native.AppendMenu(hMenu, Native.MF_OWNERDRAW, (int)menuIndex, menuIndex);
                    }

                    itemsArr.Add(mItem);
                    menuList.Add(menuIndex.ToString(), mItem);
					menuIndex++;
                }
                this.EnableMenuItem(mItem);
            }

            parentList.Add(hMenu, itemList);
        }

        #endregion

        #region PopupMenuItemCollection
        /// <summary>
        /// Collection of menu items
        /// </summary>
        public class PopupMenuItemCollection : CollectionBase
        {
			internal PopupMenu menuBase;
            private Menu parentMenu;
            //private ArrayList List;

			#region constructors

			internal PopupMenuItemCollection():base()
			{
                //this.List = new ArrayList();
			}

            internal PopupMenuItemCollection(Menu parentMenu)
                : base()
            {
                this.parentMenu = parentMenu;

                if (parentMenu is PopupMenu)
                {
                    this.menuBase = parentMenu as PopupMenu;
                }                               
			}

			#endregion

			#region public methods

			/// <summary>
			/// Adds a previously created PopupMenuItem to the end of the current menu.
			/// </summary>
			/// <param name="menuItem">The PopupMenuItem to add. </param>
			/// <returns>The zero-based index where the item is stored in the collection.</returns>
			public int Add(PopupMenuItem menuItem)
			{
                int index = -1;

                menuItem.MenuBase = parentMenu;
                if (menuBase != null)
                {
                    menuBase = menuItem.menuBase;
                    //index = menuBase.itemsArr.Add(menuItem);
                    menuBase.rebuildMenu = true;
                }
                //menuItem.Font = menuBase.font;

                //if (menuBase != null)
                //{
                //    index = menuBase.itemsArr.Add(menuItem);
                //    menuBase.rebuildMenu = true;
                //    menuItem.MenuBase = parentMenu;
                //    menuItem.Font = menuBase.font;
                //}
                //else
                //{
                //    menuItem.MenuBase = parentMenu;
                //    menuBase = menuItem.menuBase;
                //    index = menuBase.itemsArr.Add(menuItem);
                //    menuBase.rebuildMenu = true;                  
                //    menuItem.Font = menuBase.font;
                //}
				

                return List.Add(menuItem);
			}

			/// <summary>
			/// Adds an item to the System.Collections.IList.
			/// </summary>
			/// <param name="menuItem">The System.Object to add to the System.Collections.IList.</param>
			/// <returns>The zero-based index where the item is stored in the collection.</returns>
			public int Add(object menuItem)
			{
				PopupMenuItem popupItem = menuItem as PopupMenuItem;

				if (popupItem != null)
				{
					if (menuBase != null)
					{
						menuBase.itemsArr.Add(menuItem);
					}
					popupItem.MenuBase = menuBase;
					popupItem.Font = menuBase.font;
					return List.Add(popupItem);
				}

				return -1;

            }


			#endregion

			/// <summary>
			/// Gets a value indicating the total number of PopupMenuItem objects in the collection.
			/// </summary>
			public new int Count
            {
                get
                {
					return List.Count;
                }

            }

            /// <summary>
            /// Gets or sets the element at the specified index.
            /// </summary>
            /// <param name="index"></param>
            /// <returns></returns>
            public PopupMenuItem this[int index]
            {
                get
                {
                    return (PopupMenuItem)List[index];
                }
                set
                {
                    List[index] = (PopupMenuItem)value;
                }
            }

            ///// <summary>
            ///// Retrieves the PopupMenuItem at the specified indexed location in the collection.
            ///// </summary>
            ///// <param name="index">The indexed location of the PopupMenuItem in the collection. </param>
            ///// <returns>The PopupMenuItem at the specified location.</returns>
            //public virtual PopupMenuItem this[int index]
            //{
            //    get { return (PopupMenuItem)List[index]; }
            //}

			/// <summary>
			/// Determines if the specified PopupMenuItem is a member of the collection.
			/// </summary>
			/// <param name="value">The PopupMenuItem to locate in the collection.</param>
			/// <returns>true if thePopupMenuItem is a member of the collection; otherwise, false.</returns>
			public bool Contains(PopupMenuItem value)
			{
				return this.Contains((PopupMenuItem)value);
			}


			#region IList Members

			/// <summary>
			/// Removes all PopupMenuItem objects from the menu item collection.
			/// </summary>
			public new void Clear()
			{
				int count = this.List.Count;
				for (int i = 0; i < count; i++)
				{
					this.RemoveAt(0);
				}
			}
			
			/// <summary>
			/// Determines whether the System.Collections.IList contains a specific value.
			/// </summary>
			/// <param name="value">The System.Object to locate in the System.Collections.IList.</param>
			/// <returns>true if the System.Object is found in the System.Collections.IList; otherwise, false.</returns>
			public bool Contains(object value)
			{
				return this.Contains((MenuItem)value);
			}

			/// <summary>
			/// Retrieves the index of a specific item in the collection.
			/// </summary>
			/// <param name="value">The PopupMenuItem to locate in the collection.</param>
			/// <returns>The zero-based index of the item found in the collection; otherwise, -1.</returns>
			public int IndexOf(PopupMenuItem value)
			{
				return this.List.IndexOf(value);
			}


			/// <summary>
			/// Determines the index of a specific item in the System.Collections.IList.
			/// </summary>
			/// <param name="value">The System.Object to locate in the System.Collections.IList.</param>
			/// <returns>The zero-based index of the item found in the collection; otherwise, -1.</returns>
			public int IndexOf(object value)
			{
				return this.List.IndexOf((PopupMenuItem)value);
			}

			/// <summary>
			/// Inserts an item to the System.Collections.IList at the specified index.
			/// </summary>
			/// <param name="index">The zero-based index at which value should be inserted.</param>
			/// <param name="value">The System.Object to insert into the System.Collections.IList. </param>
			public void Insert(int index, object value)
			{
				PopupMenuItem popupItem = value as PopupMenuItem;
				if (popupItem != null)
				{
					if (menuBase != null)
					{
						menuBase.itemsArr.Insert(index, popupItem);
					}
					popupItem.MenuBase = menuBase;
					popupItem.Font = menuBase.font;
					List.Insert(index, popupItem);
				}
			}

            /// <summary>
            /// Gets a value indicating if the collection is a fixed size.
            /// </summary>
			public bool IsFixedSize
			{
				get { return true; }
			}

            /// <summary>
            /// Gets a value indicating if the collection is readonly.
            /// </summary>
			public bool IsReadOnly
			{
				get { return true; }
			}

            /// <summary>
            /// Removes the object from the collection.
            /// </summary>
            /// <param name="value">the value to remove</param>
			public void Remove(object value)
			{
				int index = this.List.IndexOf(value);
				if (index > -1)
				{
					this.RemoveAt(index);
				}
			}

            /// <summary>
            /// Removes an item from the collection at the specified index.
            /// </summary>
            /// <param name="index">the item to remove</param>
			public new void RemoveAt(int index)
			{
					PopupMenuItem item = (PopupMenuItem)this.List[index];
                    if (menuBase != null)
                    {
                        menuBase.RemoveMenu(index);
                    }
					this.List.RemoveAt(index);
					item.menuBase = null;
			}


			#endregion

			#region ICollection Members
            /// <summary>
            /// Copies the elements of the System.Collections.ICollection to an System.Array, starting at a particular System.Array index.
            /// </summary>
            /// <param name="array"></param>
            /// <param name="index"></param>
			public void CopyTo(Array array, int index)
			{
				this.List.CopyTo(array, index);
			}

            /// <summary>
            /// Gets a value indicating if the collection is synchronized.
            /// </summary>
			public bool IsSynchronized
			{
				get { return false; }
			}

            /// <summary>
            /// Gets an object that will be used to synchronize access to the collection.
            /// </summary>
			public object SyncRoot
			{
				get { return this; }
			}

			#endregion

			#region IEnumerable Members

            /// <summary>
            /// Returns an enumerator that iterates through a collection.
            /// </summary>
            /// <returns></returns>
			public new IEnumerator GetEnumerator()
			{
				return this.List.GetEnumerator();
			}

			#endregion
}

        #endregion

        #region MessagePump

        internal class MessagePump : MessageWindow
        {

            private PopupMenu popupMenu;
            private IntPtr hdc;
            Graphics graphics;
		
            public MessagePump(PopupMenu menu)
            {
                popupMenu = menu;
            }


            protected override void WndProc(ref Message msg)
            {
                if (msg.Msg == Native.WM_MEASUREITEM)
                {
                    Native.MEASUREITEMSTRUCT mstruct = (Native.MEASUREITEMSTRUCT)Marshal.PtrToStructure(msg.LParam, typeof(Native.MEASUREITEMSTRUCT));
                    
                    int itemHeight = 0;

                    if (mstruct.itemID == SEPARATOR_ID)
                        itemHeight = 3;
                    else
                        itemHeight = 20;

                    MeasureItemEventArgs measureArgs = new MeasureItemEventArgs(graphics, mstruct.itemData, itemHeight);
                    measureArgs.ItemWidth = 50;
                    PopupMenuItem item = null;
                    item = (PopupMenuItem)popupMenu.itemsArr[mstruct.itemData-1];
                    item.WmMeasureItem(measureArgs);

                    mstruct.itemHeight = measureArgs.ItemHeight;
                    mstruct.itemWidth = measureArgs.ItemWidth;

                    //Return filled structure
                    Marshal.StructureToPtr(mstruct, msg.LParam, false);

                }

                if (msg.Msg == Native.WM_DRAWITEM)
                {
                    Native.DRAWITEMSTRUCT dstruct = (Native.DRAWITEMSTRUCT)Marshal.PtrToStructure(msg.LParam, typeof(Native.DRAWITEMSTRUCT));
                    if (hdc != dstruct.hDC)
                    {
                        graphics = Graphics.FromHdc(dstruct.hDC);
                        hdc = dstruct.hDC;
                    }

                    PopupMenuItem item = null;
					item = (PopupMenuItem)popupMenu.itemsArr[dstruct.itemData-1];
                    DrawItemState state;

                    //Set the appropriate selected state
                    if ((dstruct.itemState & Native.ODS_SELECTED) == Native.ODS_SELECTED)
                    {
                        state = DrawItemState.Selected;
                    }
                    else
                        state = DrawItemState.None;

                    Rectangle rc = new Rectangle(dstruct.rcItem.left, dstruct.rcItem.top, dstruct.rcItem.right - dstruct.rcItem.left, dstruct.rcItem.bottom - dstruct.rcItem.top);
                    //Prepare Args
                    DrawMenuItemEventArgs drawArgs = new DrawMenuItemEventArgs(graphics, item.Font, rc, dstruct.itemData, state);

                    //Raise drawing event for inheritors
                    item.WmDrawItem(drawArgs);

                    //if ((dstruct.itemState & Native.ODS_SELECTED) == Native.ODS_SELECTED)
                    //    item.RaiseClick(new EventArgs());

                }

                msg.Result = (IntPtr)(-1);

                base.WndProc(ref msg);
            }


        }
        #endregion

        internal void EnableMenuItem(PopupMenuItem popupMenuItem)
        {
            if (IsDesignTime)
                return;

            if (popupMenuItem.Enabled)
            {
                Native.EnableMenuItem(hMainMenu, popupMenuItem.nativeIndex-1, Native.MF_BYPOSITION | Native.MF_ENABLED);
            }
            else
            {
                Native.EnableMenuItem(hMainMenu, popupMenuItem.nativeIndex-1, Native.MF_BYPOSITION | Native.MF_GRAYED);
            }
        }

        private bool m_isDesignTimeChecked;
        private bool m_isDesignTime;
        internal bool IsDesignTime
        {
            get
            {
                if (!m_isDesignTimeChecked)
                {
                    try
                    {
                        // Determine if this instance is running against .NET Framework by using the MSCoreLib PublicKeyToken
                        System.Reflection.Assembly mscorlibAssembly = typeof(int).Assembly;
                        if ((mscorlibAssembly != null))
                        {
                            if (mscorlibAssembly.FullName.ToUpper().EndsWith("B77A5C561934E089"))
                            {
                                m_isDesignTime = true;
                            }
                            else
                            {
                                m_isDesignTime = false;
                            }
                        }
                    }
                    finally
                    {
                        m_isDesignTimeChecked = true;
                    }
                }

                return m_isDesignTime;
            }
        }
    }

    /// <summary>
    /// Method that will handle the DrawMenuItem event.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void DrawMenuItemEventHandler(object sender, DrawMenuItemEventArgs e);

    /// <summary>
    /// Method that will handle the MeasureItem event.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
	public delegate void MeasureItemEventHandler(object sender, MeasureItemEventArgs e);


    #endregion

}
